﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Threading;
using Glider.Common.Objects;

namespace TileBlock
{
    public class TileBlock
    {
        private string mapDir = AppDomain.CurrentDomain.BaseDirectory + "\\Classes\\Maps\\";
        private string mpqMapDir = "textures\\Minimap\\";
        private string[] archiveNames = new string[4];

        const float TILESIZE = (float)(1600/3); // 533.3333
        const float CHUNKSIZE = (float)TILESIZE / (float)16;
        const float UNITSIZE = (float)CHUNKSIZE / (float)8;
        const float ZEROPOINT = (float)32 * TILESIZE;

        const int TOFFSET = 2;
        const int MAXTILES = 40;

        //Global vars
        private static bool time = true;
        private static bool debug = true;
        private static bool loggingStatus = true;
        private static string lastError = "";

        StormDll.ArchiveSet archive = null;
        string gameDir;

        List<tile> tileManager;

        public TileBlock()
        {
            try
            {
                tileManager = new List<tile>();
                archive = new StormDll.ArchiveSet();
                gameDir = archive.SetGameDirFromReg();
                archiveNames[0] = "patch.MPQ";
                archiveNames[1] = "expansion.MPQ";
                archiveNames[2] = "common-2.MPQ";
                archiveNames[3] = "common.MPQ";
                archive.AddArchives(archiveNames);
            }
            catch
            {
                Log("Error in TileBlock()");
            }
        }

        private class tile
        {
            public tile(string zoneIn, int xIn, int yIn, Bitmap bmpIn)
            {
                zone = zoneIn;
                x = xIn;
                y = yIn;
                bmp = bmpIn;
            }

            public string zone;
            public int x, y;
            public Bitmap bmp;
        }

        // Gets the center and 8 surrounding tiles and puts into a Bitmap
        // Need to check if tile is already loaded so no wasted time
        public Graphics GetBackground(string zone, int x, int y, Bitmap offScreenBmp, int xCenter, int yCenter, double scale)
        {
            Bitmap tempMap = null; // Holds returned bitmap

            Graphics newGraphics = Graphics.FromImage(offScreenBmp); // New graphics from offScreenBmp (passed)

            newGraphics.FillRectangle(new SolidBrush(Color.Gray), // Grey out box
                new Rectangle(0, 0, offScreenBmp.Width, offScreenBmp.Height));

            try
            {
                // Too many tiles loaded, remove extras
                //if(tileManager.Count > MAXTILES)
                //    ClearList(zone, x, y);

                // Load all adjacent tiles
                for (int i = -TOFFSET; i <= TOFFSET; i++)
                {
                    for (int j = -TOFFSET; j <= TOFFSET; j++)
                    {
                        // If tile (x,y) is in list, return it
                        tempMap = InList(zone, x+i, y+j);

                        if (tempMap != null)
                        {
                            // Do nothing...
                        }
                        // Else if the file exists, load it
                        else if (!System.IO.File.Exists(mapDir + zone + "_" + (x + i) + "_" + (y + j) + ".jpg"))
                        {
                            try
                            {
                                Log("Extract: " + zone + "_" + (x + i) + "_" + (y + j) + ".jpg");
                                tempMap = Extract(zone, x + i, y + j);
                            }
                            catch (Exception e)
                            {
                                Log("Error trying to Extract: " + zone + "_" + (x + i) + "_" + (y + j) + ".jpg");
                                Log(e.StackTrace);
                                tempMap = new Bitmap(256, 256);
                            }
                        }
                        // Else extract the file...
                        else
                        {
                            try
                            {
                                Log("Load: " + zone + "_" + (x + i) + "_" + (y + j) + ".jpg");
                                tempMap = new Bitmap(mapDir + zone + "_" + (x + i) + "_" + (y + j) + ".jpg");
                            }
                            catch (Exception e)
                            {
                                Log("Error trying to reuse: " + zone + "_" + (x + i) + "_" + (y + j) + ".jpg");
                                Log(e.StackTrace);
                                System.IO.File.Delete(mapDir + zone + "_" + (x + i) + "_" + (y + j) + ".jpg");
                                tempMap = new Bitmap(256, 256);
                            }
                        }

                        // Add newly loaded bitmap to list
                        AddToList(zone, x + i, y + j, tempMap);

                        // Print image onto the graphic
                        newGraphics.DrawImage(tempMap,
                            xCenter + i * tempMap.Width * (float)scale - i * (float)(scale * 0.8),
                            yCenter + j * tempMap.Height * (float)scale - j * (float)(scale * 0.8), 
                            (tempMap.Width * (float)scale), 
                            (tempMap.Height * (float)scale));
                    }
                }
            }
            catch (Exception e)
            {
                Log("Working in: " + zone + "_" + (x) + "_" + (y));
                Log(e.StackTrace);
            }

            // Return the graphic
            
            return newGraphics;

        }

        // Finds all non-adjacent tiles and removes them
        private void ClearList(string zone, int x, int y)
        {
            try
            {
                while (tileManager.Count > MAXTILES)
                {
                    foreach (tile selected in tileManager)
                    {
                        if (selected.zone != zone || selected.x > x + TOFFSET || selected.x < x - TOFFSET || selected.y > y + TOFFSET || selected.y < y - TOFFSET)
                        {
                            tileManager.Remove(selected);
                            break;
                        }
                    }
                }
            }
            catch
            {
                Log("Error in ClearList()");
            }
        }

        // Adds new tiles to the list
        private void AddToList(string zone, int x, int y, Bitmap bmp)
        {
            try
            {
                foreach (tile selected in tileManager)
                {
                    if (selected.x == x && selected.y == y && selected.zone == zone)
                        return;
                }
                tileManager.Add(new tile(zone, x, y, bmp));
            }
            catch
            {
                Log("Error in AddToList()");
            }
        }

        // Checks if tile is in list, if it is, return it
        private Bitmap InList(string zone, int x, int y)
        {
            try
            {
                foreach (tile selected in tileManager)
                {
                    if (selected.x == x && selected.y == y && selected.zone == zone)
                    {
                        return selected.bmp;
                    }
                }
            }
            catch
            {
                Log("Error in InList()");
            }
            return null;
        }

        // Extract file from mpq
        public Bitmap Extract(string zone, int x, int y)
        {
            bool success;
            Bitmap tempMap = null;

            try
            {
                // Check if maps folder exists, if not create
                if (!System.IO.Directory.Exists(mapDir))
                {
                    Log("Folder doesn't exist, creating.");
                    System.IO.Directory.CreateDirectory(mapDir);
                }

                // Check if md5translate.txt exists, if not extract it
                if (!System.IO.File.Exists(mapDir + "md5translate.txt"))
                {
                    Log("md5translate.txt doesn't exist, extracting.");
                    success = archive.ExtractFile(mpqMapDir + "md5translate.trs", mapDir + "md5translate.txt");
                    if (!success)
                        Log("Failed to extract md5translate.trs");
                }

                // Find hash for corresponding file
                string hash = FindHash(zone, x, y);

                if (hash == null)
                    return tempMap;

                // Print hash
                Log("Hash: " + hash);

                // Open file, extract, close file
                StormDll.File newFile = archive.OpenFile(mpqMapDir + hash);
                tempMap = newFile.Blp2Bmp(mapDir + zone + "_" + x + "_" + y + ".jpg");
                newFile.Close();

                // Return bitmap
                return tempMap;
            }
            catch
            {
                Log("Error in Extract()");
            }
            return null;
        }

        // Looks through md5translate.txt to find hash for bitmap file
        private string FindHash(string zone, int x, int y)
        {
            try
            {
                //Open md5translate.txt
                StreamReader file = System.IO.File.OpenText(mapDir + "md5translate.txt");

                string line;

                //Read each line to find hash
                while (!file.EndOfStream)
                {
                    line = file.ReadLine();
                    string[] hash;
                    char[] delimiter = { '\t' }; // Tab

                    // Line contains correct hash
                    if (line.Contains(zone) && line.Contains("map" + x + "_" + y))
                    {
                        // Separate line, close file, return hash
                        hash = line.Split(delimiter);
                        file.Close();

                        return hash[1];
                    }
                }
                Log("Hash not found.");
                return null;
            }
            catch
            {
                Log("Error in FindHash()");
            }
            return null;
        }

        // Log with time and threading debug
        private void GLog(string print)
        {
            string temp = "";
            if (print == lastError)
            {
                return;
            }
            if (time)
                temp += DateTime.Now.Hour + ":" + DateTime.Now.Minute +
                    ":" + DateTime.Now.Second + ":" + DateTime.Now.Millisecond + " - ";
            if (debug)
                temp += Convert.ToString(Thread.CurrentThread.GetHashCode()) + " - ";
            temp += print;
            GContext.Main.Log(temp);
        }

        private void Log(string print)
        {
            if (print == lastError)
            {
                return;
            }

            lastError = print;
            if (loggingStatus)
            {
                string temp = ""; 
                string file = AppDomain.CurrentDomain.BaseDirectory + "\\Classes\\Maps\\Log.txt";

                try
                {
                    TextWriter output = TextWriter.Synchronized(System.IO.File.AppendText(file));

                    if (time)
                        temp += String.Format("{0} - ", DateTime.Now.ToString("hh:mm:ss.mm"));
                    if (debug)
                        temp += Convert.ToString(Thread.CurrentThread.GetHashCode()) + " - ";
                    temp += print;
                    output.WriteLine(temp);
                    output.Close();
               }
                catch
                {
                    GLog("Error in SaveToFile()");
                }
            }
            else
            {
                GLog(print);
            }
        }
    }
}
